﻿using System;
using System.Collections.Generic;
using System.Linq;

using Microsoft.WowAddonStudio.Lua.AST;
using Microsoft.WowAddonStudio.Lua.Model;

using Function=Microsoft.WowAddonStudio.Lua.Model.Function;
using Variable=Microsoft.WowAddonStudio.Lua.Model.Variable;

namespace Microsoft.WowAddonStudio.Lua.Support
{
    /// <summary>
    /// Creates declarations for nodes in the AST.
    /// </summary>
    public static class DeclarationFactory
    {
        /// <summary>
        /// Creates a declaration for an <see cref="Identifier"/> node.
        /// </summary>
        /// <param name="identifier">The identifier.</param>
        /// <param name="isLocal">True, if the declaration is local.</param>
        /// <returns>An instance of the <see cref="Declaration"/> class.</returns>
        public static TDeclaration CreateDeclaration<TDeclaration>(Identifier identifier, bool isLocal) where TDeclaration : Declaration, new()
        {
            return new TDeclaration
                   {
                       Location = identifier.Location,
                       Name = identifier.Name,
                       IsLocal = isLocal
                   };
        }

        /// <summary>
        /// Creates a declaration for an <see cref="Identifier"/> node.
        /// </summary>
        /// <param name="identifier">The identifier.</param>
        /// <returns>An instance of the <see cref="Declaration"/> class.</returns>
        public static TDeclaration CreateDeclaration<TDeclaration>(Identifier identifier) where TDeclaration : Declaration, new()
        {
            return DeclarationFactory.CreateDeclaration<TDeclaration>(identifier, false);
        }

        /// <summary>
        /// Creates a declaration for a <see cref="FunctionDeclaration"/>.
        /// </summary>
        /// <param name="functionDeclaration">The <see cref="FunctionDeclaration"/> node.</param>
        /// <param name="isLocal">A value indicating whether the function declaration is local.</param>
        /// <returns>An instance of the <see cref="Model.Function"/> class.</returns>
        public static Declaration CreateDeclaration(FunctionDeclaration functionDeclaration, bool isLocal)
        {
            if (functionDeclaration == null)
                throw new ArgumentNullException("functionDeclaration");

            // Create function
            var function = new Function
                           {
                               Location = functionDeclaration.Location,
                               QualifiedName = functionDeclaration.FunctionName,
                               Parameters = functionDeclaration.GetParameterIdentifiers().Select(identifier => CreateDeclaration<Parameter>(identifier)).ToArray(),
                               IsLocal = isLocal
                           };

            return function;
        }

        /// <summary>
        /// Creates a declaration for a <see cref="AST.Function"/>.
        /// </summary>
        /// <param name="functionNode">The <see cref="AST.Function"/> node.</param>
        /// <returns>An instance of the <see cref="Model.Function"/> class.</returns>
        public static Function CreateDeclaration(AST.Function functionNode)
        {
            if (functionNode == null)
                throw new ArgumentNullException("functionNode");

            var function = new Function
                           {
                               Location = functionNode.Location,
                               Parameters = functionNode.GetParameterIdentifiers().Select(identifier => CreateDeclaration<Parameter>(identifier)).ToArray()
                           };

            return function;
        }

        public static Table CreateDeclaration(TableConstructor tableConstructor)
        {
            if (tableConstructor == null)
                throw new ArgumentNullException("tableConstructor");

            var table = new Table();

            foreach (Field field in tableConstructor.GetFields())
            {
                var declaration = CreateDeclaration(field);
                if (declaration != null)
                        table.Declarations.Add(declaration);
            }

            return table;
        }

        public static Declaration CreateDeclaration(Field field)
        {
            if (field == null)
                throw new ArgumentNullException("field");

            Declaration declaration = null;

            if (field.Expression != null)
            {
                // Check if field has an identifier
                if (field.Identifier != null)
                    declaration = CreateDeclaration(field.Identifier.Name, field.Expression);

                // Check if field is a literal expression
                if (field.LeftExpression != null && field.LeftExpression is Literal && ((Literal)field.LeftExpression).Type == LuaType.String)
                {
                    var fieldName = ((Literal)field.LeftExpression).GetStringValue();
                    declaration = CreateDeclaration(fieldName, field.Expression);
                }

                if (declaration != null)
                    declaration.Location = field.Location;
            }

            return declaration;
        }

        public static Variable CreateDeclaration(Literal literal)
        {
            if (literal == null)
                throw new ArgumentNullException("literal");

            return new Variable
                   {
                       Location = literal.Location,
                       Type = literal.Type
                   };
        }

        public static Variable CreateDeclaration(UnaryExpression unaryExpression)
        {
            if (unaryExpression == null)
                throw new ArgumentNullException("unaryExpression");

            var variable = new Variable
                           {
                               Location = unaryExpression.Location
                           };

            switch (unaryExpression.Operator)
            {
                case "#":
                    variable.Type = LuaType.Number;
                    break;
            }

            return variable;
        }

        public static Declaration CreateDeclaration(QualifiedName qualifiedName, Node node)
        {
            var declaration = DeclarationFactory.CreateDeclaration(qualifiedName.UnqualifiedIdentifier.Name, node);

            declaration.QualifiedName = qualifiedName;

            return declaration;
        }

        public static Declaration CreateDeclaration(string name, Node node)
        {
            if (node == null)
                throw new ArgumentNullException("node");
            if (name == null)
                throw new ArgumentNullException("name");

            Declaration declaration = null;

            if (node is AST.Function)
                declaration = CreateDeclaration((AST.Function)node);

            if (node is TableConstructor)
                declaration = CreateDeclaration((TableConstructor)node);

            if (node is Literal)
                declaration = CreateDeclaration((Literal)node);

            if (node is UnaryExpression)
                declaration = CreateDeclaration((UnaryExpression)node);

            if (declaration == null)
                declaration = new Variable();

            declaration.Name = name;

            return declaration;
        }

        public static IEnumerable<Declaration> CreateDeclarations(Assignment assignment, bool isLocal)
        {
            if (assignment == null)
                throw new ArgumentNullException("assignment");

            foreach (var singleAssignment in assignment.GetSingleAssignments())
            {
                Declaration declaration = null;

                if (singleAssignment.Key is Identifier)
                {
                    var identifier = (Identifier)singleAssignment.Key;
                    declaration = DeclarationFactory.CreateDeclaration(identifier.Name, singleAssignment.Value);
                }

                if (singleAssignment.Key is QualifiedName)
                {
                    var qualifiedName = (QualifiedName)singleAssignment.Key;
                    declaration = DeclarationFactory.CreateDeclaration(qualifiedName, singleAssignment.Value);
                }

                if (declaration == null) 
                    continue;

                declaration.Location = assignment.Location;
                declaration.IsLocal = isLocal;

                yield return declaration;
            }
        }

        public static TDeclaration CreateDeclaration<TDeclaration>(string name, string summary) where TDeclaration : Declaration, new()
        {
            if (name == null)
                throw new ArgumentNullException("name");

            return new TDeclaration
                   {
                       Name = name,
                       Summary = summary
                   };
        }

        public static TDeclaration CreateDeclaration<TDeclaration>(string name) where TDeclaration : Declaration, new()
        {
            return CreateDeclaration<TDeclaration>(name, null);
        }
    }
}